package mobi.weq.util.edinburgh;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
import java.util.Random;

import mobi.weq.dao.SensorObservationDao;
import mobi.weq.model.SensorObservation;
import mobi.weq.util.Util;

public class EdinburghDataGenerator {
	/**
	 * Create data for last n days starting from given date
	 * 
	 * @param days
	 * @throws SQLException
	 */
	public static void generateData(int days, Date d) throws SQLException {
		d.setMinutes(0);
		d.setSeconds(0);
		d.setHours(23);

		SensorObservationDao dao = new SensorObservationDao();
		for (int i = 0; i < days; i++) {
			ArrayList<SensorObservation> o = getSensorHourlyObservationsThroughoutCityEveryGrid(d);
			System.out.println(d + "#" + o.size());
			dao.addObservations(o, d);
			Util.printProgress(i, days);
			d = Util.decreaseDateByDay(d, 1);
		}
	}

	private static SensorObservation intensifySensorData(SensorObservation o) {
		Map<String, Double> map = EdinburghPlaces.getPollutedPlacesMapNormalized();
		String gridId = Util.getGridId(o.getLatitude(), o.getLongitude());
		Double intensity = map.get(gridId);
		if (intensity != null) {
			o.setNo2(o.getNo2() * intensity);
			o.setO3(o.getO3() * intensity);
			o.setPm10(o.getPm10() * intensity);
			o.setPm2p5(o.getPm2p5() * intensity);
			o.setSo2(o.getSo2() * intensity);
		}
		return o;
	}

	/**
	 * GENERATE FOR WHOLE CITY, WHOLE DAY
	 * 
	 * @param d
	 * @return
	 */
	public static ArrayList<SensorObservation> getSensorHourlyObservationsThroughoutCityEveryGrid(Date d) {
		ArrayList<SensorObservation> observations = new ArrayList<SensorObservation>();
		int day = d.getDate();
		double increment = 1.0 / EdinburghMap.GRID_DENSITY;
		for (; day == d.getDate();) {
			for (double lat = EdinburghMap.LAT1; lat < EdinburghMap.LAT2 + increment; lat += increment) {
				for (double lng = EdinburghMap.LNG1; lng < EdinburghMap.LNG2 + increment; lng += increment) {
					SensorObservation o = getSensorObservation(d);
					double latitude = Math.floor(lat * EdinburghMap.GRID_DENSITY) / EdinburghMap.GRID_DENSITY;
					double longitude = Math.floor(lng * EdinburghMap.GRID_DENSITY) / EdinburghMap.GRID_DENSITY;
					o.setLatitude(latitude);
					o.setLongitude(longitude);
					observations.add(intensifySensorData(o));
				}
			}
			d = Util.decreaseDateByHour(d, 1);
		}
		return observations;
	}

	/**
	 * GENERATE FOR WHOLE CITY, CURRENT HOUR
	 * 
	 * @return
	 */
	public static ArrayList<SensorObservation> getSensorHourlyObservationsThroughoutCityEveryGridForCurrentHour() {
		Date d = new Date();
		d.setMinutes(0);
		ArrayList<SensorObservation> observations = new ArrayList<SensorObservation>();
		double increment = 1.0 / EdinburghMap.GRID_DENSITY;
		for (double lat = EdinburghMap.LAT1; lat < EdinburghMap.LAT2 + increment; lat += increment) {
			for (double lng = EdinburghMap.LNG1; lng < EdinburghMap.LNG2 + increment; lng += increment) {
				SensorObservation o = getSensorObservation(d);
				double latitude = Math.floor(lat * EdinburghMap.GRID_DENSITY) / EdinburghMap.GRID_DENSITY;
				double longitude = Math.floor(lng * EdinburghMap.GRID_DENSITY) / EdinburghMap.GRID_DENSITY;
				o.setLatitude(latitude);
				o.setLongitude(longitude);
				observations.add(o);
			}
		}
		return observations;
	}

	public static ArrayList<SensorObservation> generateDataForAGridNDays(int days, Date d, double latitude, double longitude) {
		d.setMinutes(0);
		d.setSeconds(0);
		d.setHours(23);
		ArrayList<SensorObservation> observations = new ArrayList<SensorObservation>();
		for (int i = 0; i < days * 24; i++) {
			SensorObservation o = getSensorHourlyObservationsForAGrid(d, latitude, longitude);
			d = Util.decreaseDateByHour(d, 1);
			observations.add(o);
		}
		return observations;
	}

	/**
	 * GENERATE FOR a GRID, CURRENT HOUR
	 * 
	 * @return
	 */
	public static SensorObservation getSensorHourlyObservationsForAGrid(Date d, double latitude, double longitude) {
		d.setMinutes(0);
		SensorObservation o = getSensorObservation(d);
		o.setLatitude(latitude);
		o.setLongitude(longitude);
		return o;
	}

	public static ArrayList<SensorObservation> getSensorObservations(int lastNDays) {
		Date d = new Date();
		d.setMinutes(0);
		d.setSeconds(0);
		ArrayList<SensorObservation> observations = new ArrayList<SensorObservation>();
		int total = lastNDays * 24;
		int time = d.getHours();
		for (int i = 0; i < total; i++) {
			SensorObservation o = getSensorObservation(d);
			observations.add(o);
			d = Util.decreaseDateByHour(d, 1);
		}
		return observations;
	}

	public static SensorObservation getSensorObservation(Date d) {
		SensorObservation o = new SensorObservation();
		int time = d.getHours();
		o.setTimestamp(d);
		o.setO3(getO3(time));
		o.setPm10(getPM10(time));
		o.setPm2p5(getPM2P5(time));
		o.setSo2(getSO2(time));
		o.setNo2(getNO2(time));
		return o;
	}

	private static double getCO(int time) {
		// using EdinburghPollutionSubstanceStatistics
		Random random = new Random();
		Double mean = EdinburghPollutionSubstanceStatistics.coMap.get(time).get(0);
		Double variance = EdinburghPollutionSubstanceStatistics.coMap.get(time).get(1);
		return mean + random.nextGaussian() * variance;
	}

	private static double getNO2(int time) {
		Random random = new Random();
		Double mean = EdinburghPollutionSubstanceStatistics.no2Map.get(time).get(0);
		Double variance = EdinburghPollutionSubstanceStatistics.no2Map.get(time).get(1);
		return mean + random.nextGaussian() * variance;
	}

	private static double getSO2(int time) {
		Random random = new Random();
		Double mean = EdinburghPollutionSubstanceStatistics.so2Map.get(time).get(0);
		Double variance = EdinburghPollutionSubstanceStatistics.so2Map.get(time).get(1);
		return mean + random.nextGaussian() * variance;
	}

	private static double getO3(int time) {
		Random random = new Random();
		Double mean = EdinburghPollutionSubstanceStatistics.o3Map.get(time).get(0);
		Double variance = EdinburghPollutionSubstanceStatistics.o3Map.get(time).get(1);
		return mean + random.nextGaussian() * variance;
	}

	private static double getPM10(int time) {
		Random random = new Random();
		Double mean = EdinburghPollutionSubstanceStatistics.pm10Map.get(time).get(0);
		Double variance = EdinburghPollutionSubstanceStatistics.pm10Map.get(time).get(1);
		return mean + random.nextGaussian() * variance;
	}

	private static double getPM2P5(int time) {
		Random random = new Random();
		Double mean = EdinburghPollutionSubstanceStatistics.pm2p5Map.get(time).get(0);
		Double variance = EdinburghPollutionSubstanceStatistics.pm2p5Map.get(time).get(1);
		return mean + random.nextGaussian() * variance;
	}

	private static double getLatitude() {
		double k = 55.940778962167435, j = 55.9413520357129;
		double diff = k - j;
		Random r = new Random();
		return r.nextDouble() * diff + j;
	}

	private static double getLongitude() {
		double j = -3.2064564836059617, k = -3.2043107163940476;
		double diff = k - j;
		Random r = new Random();
		return r.nextDouble() * diff + j;
	}

	public static SensorObservation getOneDummyObservation() {
		return getSensorObservation(new Date());
	}
}
